package cache

import (
    "context"
    "fmt"
    "net"
    "strconv"
    "strings"
    "sync"
    "time"

    "github.com/go-redis/redis/v7"
)

// ----------------------------------------
//  Redis 客户端包装
// ----------------------------------------
type RedisClient struct {
    *redis.Client
    name        string
    config      *RedisConfig
    mu          *sync.Mutex
    connectedAt time.Time
    pingedAt    time.Time
}

func CreateRedisClient(name string, config *RedisConfig) (*RedisClient, error) {
    client := &RedisClient{name: name, config: config.Copy(), mu: &sync.Mutex{}}

    if err := client.Connect(); err != nil {
        return nil, err
    } else {
        return client, nil
    }
}

func CreateRedisClientWithDriver(name string, driver *redis.Client) (*RedisClient, error) {
    client := &RedisClient{
        name:        name,
        Client:      driver,
        config:      &RedisConfig{},
        mu:          &sync.Mutex{},
        connectedAt: time.Now().Local(),
        pingedAt:    time.Now().Local(),
    }

    opts := driver.Options()
    config := &RedisConfig{}

    if strings.Contains(opts.Addr, ":") {
        if host, p, err := net.SplitHostPort(opts.Addr); err != nil {
            return nil, err
        } else {
            config.Host = host
            if port, err := strconv.Atoi(p); err != nil {
                return nil, err
            } else {
                config.Port = port
            }
        }
    } else {
        config.Host = opts.Addr
        config.Port = 6379
    }

    config.Password = opts.Password
    config.Database = opts.DB
    config.MaxRetries = opts.MaxRetries
    config.PoolSize = opts.PoolSize
    config.Ping = true
    config.DialTimeout = int(opts.DialTimeout)
    config.ReadTimeout = int(opts.ReadTimeout)
    config.WriteTimeout = int(opts.WriteTimeout)

    client.config = config

    return client, nil
}

func (client *RedisClient) WithCtx(ctx context.Context) *RedisClient {
    copyClient := client.Copy()
    copyClient.Client = client.Client.WithContext(ctx)
    return copyClient
}

func (client *RedisClient) Name() string {
    return client.name
}

func (client *RedisClient) Config() *RedisConfig {
    return client.config.Copy()
}

func (client *RedisClient) Ping() error {
    if client.config.Ping && client.Client != nil {
        client.pingedAt = time.Now().Local()
        return client.Client.Ping().Err()
    } else {
        return nil
    }
}

func (client *RedisClient) Connect() error {
    client.mu.Lock()
    defer client.mu.Unlock()

    if client.Client != nil {
        return client.Ping()
    }

    config := &redis.Options{
        Network:    "tcp",
        Addr:       fmt.Sprintf("%s:%d", client.config.Host, client.config.Port),
        Password:   client.config.Password,
        DB:         client.config.Database,
        MaxRetries: client.config.MaxRetries,
        PoolSize:   client.config.PoolSize,
    }
    if client.config.DialTimeout > 0 {
        config.DialTimeout = time.Duration(client.config.DialTimeout) * time.Millisecond
    }
    if client.config.ReadTimeout > 0 {
        config.ReadTimeout = time.Duration(client.config.ReadTimeout) * time.Millisecond
    }
    if client.config.WriteTimeout > 0 {
        config.WriteTimeout = time.Duration(client.config.WriteTimeout) * time.Millisecond
    }
    client.Client = redis.NewClient(config)

    client.connectedAt = time.Now().Local()
    return client.Ping()
}

func (client *RedisClient) Close() error {
    client.mu.Lock()
    defer client.mu.Unlock()

    if client.Client == nil {
        return nil
    } else {
        err := client.Client.Close()
        client.Client = nil // 强制丢弃
        return err
    }
}

func (client *RedisClient) Copy() *RedisClient {
    client.mu.Lock()
    defer client.mu.Unlock()

    return &RedisClient{
        name:        client.name,
        config:      client.Config(),
        mu:          &sync.Mutex{},
        Client:      client.Client,
        connectedAt: client.connectedAt,
        pingedAt:    client.pingedAt,
    }
}

func (client *RedisClient) Reconnect() error {
    _ = client.Close()
    return client.Connect()
}

func (client *RedisClient) Locker(key string, args ...interface{}) *RedisLocker {
    return CreateRedisLocker(client, key, args...)
}

func (client *RedisClient) List(key string, args ...interface{}) *RedisList {
    return CreateRedisList(client, key, args...)
}

func (client *RedisClient) JSON(key string, args ...interface{}) *RedisJSON {
    return CreateRedisJSON(client, key, args...)
}

func (client *RedisClient) Counter(key string, args ...interface{}) *RedisCounter {
    return CreateRedisCounter(client, key, args...)
}

func (client *RedisClient) Marker(key string, args ...interface{}) *RedisMarker {
    return CreateRedisMarker(client, key, args...)
}
